﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Clipping.Utils;
using Clipping.Infrastructure.Models;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Windows.Input;
using System.Windows.Threading;
using System.Windows;
using System.Threading;
using System.Diagnostics;
namespace ClippingMaster
{
    public class SearchSession : GalaSoft.MvvmLight.ViewModelBase
    {

        ConcurrentDictionary<ISearchEngine, Session> _sessionDic = new ConcurrentDictionary<ISearchEngine, Session>();
        SearchObject _searchObj;

        private OpenHistoryRepository openHisRepository = new OpenHistoryRepository();
        private UploadHistoryRepository uploadHistoryRepository = new UploadHistoryRepository();
        private Dictionary<string, ClientInfo> allClientsDic;
        private Log4cb.ILog4cbHelper logHelper;
        public SearchSession(SearchObject searchObj, List<ISearchEngine> searchEngines, Log4cb.ILog4cbHelper logHelper)
        {
            this._searchObj = searchObj;
            this.logHelper = logHelper;
            foreach (var se in searchEngines)
            {
                this._sessionDic[se] = new Session() { Downloader = new HTMLDownloader() };
            }
            initCommands();

            this.allClientsDic = ClientInfoProvider.GetInstance().GetAllClientInfoDic();
        }

        private void initCommands()
        {
            this.SearchCommand = new GalaSoft.MvvmLight.Command.RelayCommand(search);
            this.DoubleClickCommand = new GalaSoft.MvvmLight.Command.RelayCommand<NewsModel>(MakeClipping);
            this.GoPrePageCommand = new GalaSoft.MvvmLight.Command.RelayCommand(goPrePage, canGoPrePage);
            this.GoNextPageCommand = new GalaSoft.MvvmLight.Command.RelayCommand(goNextPage, canGoNextPage);
            this.MakeClippingCommand = new GalaSoft.MvvmLight.Command.RelayCommand<NewsModel>(MakeClipping);
            this.GetSameNewsCommand = new GalaSoft.MvvmLight.Command.RelayCommand<NewsModel>(getSameNews);
            this.OpenLinkInBrowserCommand = new GalaSoft.MvvmLight.Command.RelayCommand<NewsModel>(openLink);
            this.CopyLinkCommand = new GalaSoft.MvvmLight.Command.RelayCommand<NewsModel>(copyLink);
            this.MultiUploadCommand = new GalaSoft.MvvmLight.Command.RelayCommand(multiUpload, canMultiUpload);
        }

        private void multiUpload()
        {
            var selectedNews = this.SearchResult.Where(m => m.IsSelected).ToList();
            Clipping.Utils.OpenHistoryRepository rep = new Clipping.Utils.OpenHistoryRepository();
            foreach (var n in selectedNews)
            {
                rep.AddHistory(new OpenHistory() { Url = n.News.Url });
                n.Opened = true;
            }
            ClippingEditor.MultiClippingUpload win = new ClippingEditor.MultiClippingUpload(selectedNews.Select(n => n.News).ToList(), this.logHelper);
            win.Closed += new EventHandler(win_Closed);
            win.Show();
        }

        void win_Closed(object sender, EventArgs e)
        {
            ClippingEditor.MultiClippingUpload clippingEditor = sender as ClippingEditor.MultiClippingUpload;
            clippingEditor.Closed -= new EventHandler(win_Closed);
            try
            {
                if (this.SearchResult == null)
                    return;
                var newsModelList = this.SearchResult.ToList();
                List<NewsModel> models = new List<NewsModel>();

                foreach (var n in clippingEditor.ProcessedNews)
                {
                    var model = newsModelList.FirstOrDefault(nm => nm.News == n);
                    if (model != null)
                        models.Add(model);
                }
                updateNewsHistory(models);

            }
            catch (Exception ex)
            {
            }
        }
        private bool canMultiUpload()
        {
            if (this.SearchResult == null || this.SearchResult.Count == 0)
                return false;
            else if (this.SearchResult.Any(m => m.IsSelected))
                return true;
            else
                return false;
        }
        private void goPrePage()
        {
            if (this.CurrentPageNo <= 1)
                return;
            search(this.CurrentPageNo - 1);
        }

        private bool canGoPrePage()
        {
            if (this.CurrentPageNo <= 1)
                return false;
            else
                return true;
        }

        private void goNextPage()
        {
            search(this.CurrentPageNo + 1);
        }

        private bool canGoNextPage()
        {
            return true;
        }


        private void search()
        {
            search(1);
        }

        private void search(int pageNo)
        {

            this.IsBusy = true;

            List<Task> tasks = new List<Task>();
            Action<object> act = (obj) => { Search(obj as ISearchEngine, pageNo); };
            foreach (var kv in this._sessionDic)
            {
                Task t = Task.Factory.StartNew(act, kv.Key);
                tasks.Add(t);
            }
            Action<object> waitAct = (obj) => { WaitForSearchComplete(obj as List<Task>, pageNo); };
            Task.Factory.StartNew(waitAct, tasks);
        }

        private void WaitForSearchComplete(List<Task> tasks, int pageNo)
        {
            Task.WaitAll(tasks.ToArray());
            DispatcherHelper.InvokeAction((Action)(() =>
             {
                 ObservableCollection<NewsModel> news = new ObservableCollection<NewsModel>();
                 bool allSucceed = true;
                 string errMsg = string.Empty;
                 foreach (var kv in this._sessionDic)
                 {
                     Session s = this._sessionDic[kv.Key];

                     SearchResultCache cache;
                     if (s.Cache.TryGetValue(pageNo, out cache))
                     {
                         if (cache.Status == SearchStatus.Succeed)
                         {
                             foreach (var n in cache.SearchResult)
                             {
                                 n.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(n_PropertyChanged);
                                 n.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(n_PropertyChanged);
                                 news.Add(n);
                             }
                         }
                         else
                         {
                             allSucceed = false;
                             if (cache.Status == SearchStatus.DownloadHtmlFailed)
                             {
                                 errMsg += string.Format("[{0}]打开页面失败；", kv.Key.Name);
                             }
                             else
                             {
                                 errMsg += string.Format("[{0}]解析失败；", kv.Key.Name);
                             }
                         }
                     }
                     this.AllSucceed = allSucceed;
                     this.StatusMsg = errMsg;
                 }
                 this.CurrentPageNo = pageNo;
                 this.SearchResult = news;
                 this.IsBusy = false;
             }));
        }

        void n_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsSelected")
            {
                (this.MultiUploadCommand as GalaSoft.MvvmLight.Command.RelayCommand).RaiseCanExecuteChanged();
            }
        }

        private void Search(ISearchEngine searchEngine, int pageNo)
        {
            Session session;
            this._sessionDic.TryGetValue(searchEngine, out session);


            if (!session.Cache.ContainsKey(pageNo))
            {

                if (pageNo == 1)
                {
                    var url = searchEngine.ConstructUrl(this._searchObj);
                    Search(searchEngine, session, url, pageNo);
                }
                else if (session.Cache.ContainsKey(pageNo - 1))
                {
                    var c = session.Cache[pageNo - 1];
                    if (c.Status == SearchStatus.Succeed && !string.IsNullOrEmpty(c.NextPageUrl))
                    {
                        Search(searchEngine, session, new Uri(c.NextPageUrl), pageNo);
                    }
                }
            }
            else
            {
                //缓存中已有，不再搜索
            }
        }

        private void Search(ISearchEngine searchEngine, Session session, Uri url, int pageNo)
        {
            string html = null;
            for (int i = 0; i < 3; i++)
            {
                try
                {
                    html = session.Downloader.GetHtml(url, searchEngine.ContentEncoding);
                    if (html != null)
                        break;
                }
                catch (Exception ex)
                {
                    this.logHelper.LogWarnMsg(ex, "下载网页异常，Url={0}", url.OriginalString);
                }
            }
            if (!string.IsNullOrEmpty(html))
            {
                List<News> news;
                string nextPageUrl;
                try
                {
                    searchEngine.Parse(url, html, out news, out nextPageUrl);
                    List<NewsModel> newsModels = new List<NewsModel>();
                    if (news != null)
                    {
                        foreach (var n in news)
                        {
                            NewsModel nm = new NewsModel(n, searchEngine);
                            newsModels.Add(nm);
                        }
                        updateNewsHistory(newsModels);
                    }

                    session.Cache[pageNo] = new SearchResultCache() { PageNo = pageNo, Url = url.OriginalString, Status = SearchStatus.Succeed, NextPageUrl = nextPageUrl, SearchResult = newsModels };

                    if (news == null || news.Count == 0)
                        session.Cache[pageNo].Status = SearchStatus.ParseFailed;

                }
                catch (Exception ex)
                {
                    session.Cache[pageNo] = new SearchResultCache() { PageNo = pageNo, Url = url.OriginalString, Status = SearchStatus.ParseFailed };

                }
            }
            else
            {
                session.Cache[pageNo] = new SearchResultCache() { PageNo = pageNo, Url = url.OriginalString, Status = SearchStatus.DownloadHtmlFailed };
            }
        }

        private void MakeClipping(NewsModel news)
        {

            if (news == null)
                return;
            ClippingEditor.MainWindow clippingEditor = new ClippingEditor.MainWindow(news.News);
            clippingEditor.Show();
            Clipping.Utils.OpenHistoryRepository rep = new Clipping.Utils.OpenHistoryRepository();
            rep.AddHistory(new OpenHistory() { Url = news.News.Url });
            news.Opened = true;
            clippingEditor.Closed += new EventHandler(clippingEditor_Closed);
        }


        void clippingEditor_Closed(object sender, EventArgs e)
        {
            ClippingEditor.MainWindow clippingEditor = sender as ClippingEditor.MainWindow;
            clippingEditor.Closed -= new EventHandler(clippingEditor_Closed);
            try
            {
                if (this.SearchResult == null)
                    return;
                var newsModelList = this.SearchResult.ToList();
                var model = newsModelList.FirstOrDefault(nm => nm.News == clippingEditor.News);
                if (model != null)
                {
                    updateNewsHistory(new List<NewsModel>() { model });
                }
            }
            catch (Exception ex)
            {
            }
        }


        private void getSameNews(NewsModel news)
        {

            if (string.IsNullOrEmpty(news.News.SameNewsUrl) || news.News.SameNewsRetrived)
                return;
            this.IsBusy = true;
            Task task = Task.Factory.StartNew(getSameNews, news);
        }

        private void getSameNews(object newsObj)
        {
            try
            {
                NewsModel news = newsObj as NewsModel;
                Uri address = new Uri(news.News.SameNewsUrl);
                Session session;
                if (this._sessionDic.TryGetValue(news.SearchEngine, out session))
                {
                    while (address != null)
                    {
                        string html = session.Downloader.GetHtml(address, news.SearchEngine.ContentEncoding);
                        List<News> sameNews;
                        string nextPageUrl;
                        news.SearchEngine.Parse(address, html, out sameNews, out nextPageUrl);

                        if (sameNews != null)
                        {
                            foreach (var n in sameNews)
                            {
                                news.News.AddSameNews(n);
                            }
                        }
                        if (!string.IsNullOrEmpty(nextPageUrl) && Uri.IsWellFormedUriString(nextPageUrl, UriKind.Absolute))
                            address = new Uri(nextPageUrl);
                        else
                            address = null;
                        if (address != null)
                            Thread.Sleep(1000);
                    }
                }
                if (news.News.SameNews != null && news.News.SameNews.Count > 0)
                {
                    List<NewsModel> models = new List<NewsModel>();
                    foreach (var n in news.News.SameNews)
                    {
                        NewsModel nm = new NewsModel(n, news.SearchEngine);
                        models.Add(nm);
                    }
                    updateNewsHistory(models);
                    int index = session.Cache[this.CurrentPageNo].SearchResult.IndexOf(news);
                    session.Cache[this.CurrentPageNo].SearchResult.InsertRange(index + 1, models);

                    DispatcherHelper.InvokeAction(() =>
                    {
                        index = this.SearchResult.IndexOf(news);
                        foreach (var nm in models)
                        {
                            nm.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(n_PropertyChanged);
                            nm.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(n_PropertyChanged);
                            this.SearchResult.Insert(index + 1, nm);
                            index++;
                        }
                    });
                }
                news.News.SameNewsRetrived = true;
                news.HasUnretrivedSameNews = false;
            }
            catch (Exception ex)
            {

            }
            finally
            {
                DispatcherHelper.InvokeAction(() => { this.IsBusy = false; });
            }


        }

        private bool canGetSameNews(NewsModel news)
        {


            if (news == null || news.News == null)
                return false;

            return news.HasUnretrivedSameNews;
        }

        /// <summary>
        /// 打开链接
        /// </summary>
        /// <param name="news"></param>
        private void openLink(NewsModel news)
        {

            if (news == null || news.News == null)
                return;
            var p = System.Diagnostics.Process.Start(news.News.Url);
            //if (p == null)
            //    System.Diagnostics.Process.Start("iexplore.exe", news.News.Url);
        }
        /// <summary>
        /// 复制链接到剪切板
        /// </summary>
        /// <param name="news"></param>
        private void copyLink(NewsModel news)
        {

            if (news == null || news.News == null)
                return;
            try
            {
                Clipboard.SetText(news.News.Url);
            }
            catch { }
        }

        private void updateNewsHistory(IEnumerable<NewsModel> news)
        {
            if (news == null || !news.Any())
                return;
            Task t = Task.Factory.StartNew(updateNewsHistory, news);
        }

        private void updateNewsHistory(object newsObj)
        {
            IEnumerable<NewsModel> news = newsObj as IEnumerable<NewsModel>;
            List<string> urls = news.Select(n => n.News.Url).ToList();
            var uploadedClientIdDic = uploadHistoryRepository.GetUploadedClients(urls);
            var openedDic = openHisRepository.IsOpened(urls);
            foreach (var n in news)
            {
                DispatcherHelper.InvokeAction(() =>
                {
                    List<string> uploadedClassIds;
                    uploadedClientIdDic.TryGetValue(n.News.Url, out uploadedClassIds);
                    if (uploadedClassIds != null)
                    {
                        var classes = getClientInfos(uploadedClassIds);
                        n.UploadedClients = classes;
                    }
                    n.Opened = openedDic[n.News.Url];
                });
            }

            List<UploadHistory> historyRecords = new List<UploadHistory>();
            foreach (var n in news)
            {
                if (n.News != null && (n.UploadedClients == null || n.UploadedClients.Count == 0))
                {
                    UploadHistoryService.getClientIdByLinkPortTypeClient client = new UploadHistoryService.getClientIdByLinkPortTypeClient();
                    try
                    {
                        var clientIds = client.getClientIdByLink(n.News.Url);
                        if (clientIds != null && clientIds.Count > 0)
                        {
                            foreach (var clientId in clientIds)
                            {
                                historyRecords.Add(new UploadHistory() { Url = n.News.Url, ClientId = clientId });
                            }
                            var clients = getClientInfos(clientIds);
                            DispatcherHelper.InvokeAction(() =>
                            {
                                n.UploadedClients = clients;
                            });
                        }
                    }
                    catch (Exception ex)
                    {
                        logHelper.LogWarnMsg(ex, "通过WebService获取上传历史异常");
                    }
                }
            }
            if (historyRecords.Count > 0)
                uploadHistoryRepository.AddHistory(historyRecords);
        }

        private ObservableCollection<ClientInfo> getClientInfos(IEnumerable<string> clientIds)
        {
            var clients = new ObservableCollection<ClientInfo>();
            foreach (var classId in clientIds)
            {
                ClientInfo ci;
                if (this.allClientsDic.TryGetValue(classId, out ci))
                {
                    clients.Add(ci);
                }
            }
            return clients;
        }

        #region 属性

        private int _currentPageNo;
        /// <summary>
        /// 当前页码
        /// </summary>
        public int CurrentPageNo
        {
            get { return _currentPageNo; }
            set
            {
                if (value == this._currentPageNo)
                    return;
                _currentPageNo = value;
                RaisePropertyChanged(() => this.CurrentPageNo);
                (GoPrePageCommand as GalaSoft.MvvmLight.Command.RelayCommand).RaiseCanExecuteChanged();

            }
        }


        private int _totalPageCount;
        public int TotalPageCount
        {
            get { return _totalPageCount; }
            set
            {
                if (value == this._totalPageCount)
                    return;
                _totalPageCount = value;
                RaisePropertyChanged(() => this.TotalPageCount);
            }
        }

        private ObservableCollection<NewsModel> _searchResult;
        public ObservableCollection<NewsModel> SearchResult
        {
            get { return _searchResult; }
            set
            {
                _searchResult = value;
                RaisePropertyChanged(() => this.SearchResult);
            }
        }

        private NewsModel _selectedNews;
        public NewsModel SelectedNews
        {
            get { return _selectedNews; }
            set
            {
                if (value == this._selectedNews)
                    return;
                _selectedNews = value;
                RaisePropertyChanged(() => this.SelectedNews);
            }
        }

        private bool _isBusy;
        public bool IsBusy
        {
            get
            {
                return _isBusy;
            }
            set
            {
                if (value == this._isBusy)
                    return;
                _isBusy = value;
                RaisePropertyChanged(() => this.IsBusy);
            }
        }


        private bool? _allSucceed;

        public bool? AllSucceed
        {
            get { return _allSucceed; }
            set
            {
                if (value == this._allSucceed)
                    return;
                _allSucceed = value;
                RaisePropertyChanged(() => this.AllSucceed);
            }
        }


        private string _statusMsg;

        public string StatusMsg
        {
            get { return _statusMsg; }
            set
            {
                if (value == this._statusMsg)
                    return;
                _statusMsg = value;
                RaisePropertyChanged(() => this.StatusMsg);

            }
        }
        #endregion

        /// <summary>
        /// 搜索
        /// </summary>
        public ICommand SearchCommand { get; private set; }

        /// <summary>
        /// 双击
        /// </summary>
        public ICommand DoubleClickCommand { get; private set; }

        /// <summary>
        /// 下一页
        /// </summary>
        public ICommand GoNextPageCommand { get; private set; }

        /// <summary>
        /// 上一页
        /// </summary>
        public ICommand GoPrePageCommand { get; private set; }

        /// <summary>
        /// 批量上传
        /// </summary>
        public ICommand MultiUploadCommand { get; private set; }

        /// <summary>
        /// 获取相同新闻
        /// </summary>
        public ICommand GetSameNewsCommand { get; private set; }

        /// <summary>
        /// 生成剪报
        /// </summary>
        public ICommand MakeClippingCommand { get; private set; }

        /// <summary>
        /// 复制链接
        /// </summary>
        public ICommand CopyLinkCommand { get; private set; }

        /// <summary>
        /// 在浏览器中打开
        /// </summary>
        public ICommand OpenLinkInBrowserCommand { get; private set; }

        class Session
        {
            public IHtmlDownload Downloader;
            public Dictionary<int, SearchResultCache> Cache = new Dictionary<int, SearchResultCache>();
        }

        class SearchResultCache
        {

            public int PageNo;

            public SearchStatus Status;

            public List<NewsModel> SearchResult;

            public string Url;

            public string NextPageUrl;
        }
    }
}
